/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004-2005  Benoit Blanchon                      */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/


/******************************************************************/
/* Build configuration                                            */
/******************************************************************/

#define TRACE_LEVEL	2	// warning level


/******************************************************************/
/* Includes                                                       */
/******************************************************************/

// module's interface
#include "Scanner.h"

// standard headers
#include <ctype.h>
#include <stdio.h>
#include <tchar.h>

// project's headers
#include "Config.h"
#include "strlcpy.h"
#include "Trace.h"


/******************************************************************/
/* Internal constants                                             */
/******************************************************************/

static LPCTSTR szNoneName	= TEXT("None") ;
static LPCTSTR szClamWinName	= TEXT("ClamWin") ;
static LPCTSTR szClamWinKey	= TEXT("Software\\ClamWin") ;
static LPCTSTR szClamWinPathValue = TEXT("Path") ;
static LPCTSTR szClamWinConf	= TEXT("ClamWin.conf") ;


/******************************************************************/
/* Internal data types                                            */
/******************************************************************/

typedef struct {
  TCHAR		szScanner[MAX_PATH] ;
  TCHAR		szDatabase[MAX_PATH] ;
} CLAMWINCONF ;

typedef struct {
  UINT		nScanner ;
  CLAMWINCONF	clamwinconf ;
} INTERNALDATA ;


/******************************************************************/
/* Internal data                                                  */
/******************************************************************/

static INTERNALDATA	g_data ;


/******************************************************************/
/* Internal function                                              */
/******************************************************************/

BOOL _Scanner_ClamWin_Configure (CLAMWINCONF*) ;

UINT _Scanner_ClamWin_ScanFile (CLAMWINCONF*, LPCTSTR, LPTSTR szOutput, UINT nOutputMax) ;

BOOL _Scanner_ClamWin_FindConfFilePath (LPTSTR) ;


/******************************************************************/
/* Exported function : Init                                       */
/******************************************************************/

BOOL Scanner_Init () 
{
  BOOL	bResult ;

  TRACE ;
  
  //
  // Which antivirus ?
  //

  // -> NULL, autodetect
  if( !Config_GetString(CFGSTR_ANTIVIRUS) ) 
    {
      if( _Scanner_ClamWin_Configure (&g_data.clamwinconf) )
	g_data.nScanner = SCANNER_CLAMWIN ;
      else
	g_data.nScanner = SCANNER_NONE ;
      bResult = TRUE ;
    } 
  // -> ClamWin
  else if( !_tcsicmp(szClamWinName,Config_GetString(CFGSTR_ANTIVIRUS)) ) 
    {
      bResult = _Scanner_ClamWin_Configure (&g_data.clamwinconf) ;  
      g_data.nScanner = SCANNER_CLAMWIN ;
    }
  // -> None
  else if( !_tcsicmp(szNoneName,Config_GetString(CFGSTR_ANTIVIRUS)) ) 
    {
      g_data.nScanner = SCANNER_NONE ;
      bResult = TRUE ;
    }
  // -> Other
  else bResult = FALSE ;
  
  // if failed, then set to "none"
  if( ! bResult ) g_data.nScanner = SCANNER_NONE ;

  return bResult ;
}


/******************************************************************/
/* Exported function : Uninit                                     */
/******************************************************************/

VOID Scanner_Uninit () 
{
  TRACE ;

  // update config
  switch( g_data.nScanner )
    {
    case SCANNER_CLAMWIN:
      Config_SetString (CFGSTR_ANTIVIRUS, szClamWinName) ;
      break ;
    default:
      Config_SetString (CFGSTR_ANTIVIRUS, szNoneName) ;
    }    
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL Scanner_SetScanner (UINT nScanner) 
{
  BOOL bResult = FALSE ;
  
  TRACE ;

  switch( nScanner )
    {
    case SCANNER_CLAMWIN:
      bResult = _Scanner_ClamWin_Configure (&g_data.clamwinconf)  ;
      break ;
    case SCANNER_NONE:
      bResult = TRUE ;
      break ;
    }

  if( bResult )
    g_data.nScanner = nScanner ; 
  else
    g_data.nScanner = SCANNER_NONE ; 

  return bResult ;
}

/******************************************************************/
/* Exported function : IsConfigured                               */
/******************************************************************/

BOOL Scanner_IsConfigured ()
{
  TRACE ;
  
  return g_data.nScanner!=SCANNER_NONE ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

UINT Scanner_GetScanner  () 
{
  TRACE ;
  
  return g_data.nScanner ;
}


/******************************************************************/
/* Exported function : IsScanner                                  */
/******************************************************************/

BOOL Scanner_IsScanner (LPCTSTR szPath) 
{
  if( g_data.nScanner==SCANNER_CLAMWIN )
    return !_tcscmp(g_data.clamwinconf.szScanner,szPath) ;
  
  return FALSE ;
}


/******************************************************************/
/* Exported function : ScanFile                                   */
/******************************************************************/

UINT Scanner_ScanFile (LPCTSTR szPath, LPTSTR szOutput, UINT nOutputMax) 
{
  TRACE_INFO ("Path = %s\n", szPath) ;

  // avoid scanning the scanner
  if( Scanner_IsScanner(szPath) ) {
    TRACE_INFO (TEXT("Prevent scanning the scanner")) ;
    return FALSE ;
  }

  // don't scan if not configured
  if( g_data.nScanner != SCANNER_CLAMWIN ) {
    TRACE_INFO (TEXT("No scanner configured")) ;
    return 0 ;
  }
  
  return _Scanner_ClamWin_ScanFile (&g_data.clamwinconf, szPath, szOutput, nOutputMax) ;
}


/******************************************************************/
/* Internal function                                              */
/******************************************************************/

BOOL _Scanner_ClamWin_FindConfFilePath (LPTSTR szPath) 
{
  HKEY	hkey ;
  LONG	nResult ;
  DWORD dwType, dwSize ;
  UINT	nLen ;
  TCHAR szValue[MAX_PATH] ;

  // open key on HKCU
  nResult = RegOpenKeyEx (HKEY_CURRENT_USER, szClamWinKey, 0, KEY_READ, &hkey) ;
  if( ERROR_SUCCESS!=nResult )
    {
      TRACE_WARNING (TEXT("Failed to open key HKCU\\%s (error=%d)\n"),
		     szClamWinKey, nResult) ;
      
      // else try on HKLM
      nResult = RegOpenKeyEx (HKEY_LOCAL_MACHINE, szClamWinKey, 0, KEY_READ, &hkey) ;      
      if( ERROR_SUCCESS!=nResult )
	{
	  TRACE_WARNING (TEXT("Failed to open key HKLM\\%s (error=%d)\n"),
			 szClamWinKey, nResult) ;
	  return FALSE ;
	}
    }

  // read install path
  dwSize = sizeof(szValue) ;
  nResult = RegQueryValueEx (hkey, szClamWinPathValue, NULL, 
			     &dwType, (BYTE*)szValue, &dwSize);
  
  // ok ?
  if( ERROR_SUCCESS!=nResult || dwSize==0 ) {
      TRACE_ERROR (TEXT("Failed to read ClamWin path (error=%d)\n"),nResult) ;
      CloseHandle(hkey) ;
      return FALSE ;
    }

  // expand env strings
  nLen = ExpandEnvironmentStrings (szValue, szPath, MAX_PATH) ;
  if( ! nLen ) { 
    TRACE_ERROR (TEXT("ExpandEnvironmentStrings failed (error=%d)\n"), GetLastError()) ;
    CloseHandle(hkey) ;
    return FALSE ;    
  }

  // get conf file path 
  _tcscat (szPath, TEXT("\\")) ;
  _tcscat (szPath, szClamWinConf) ;
  
  CloseHandle(hkey) ;

  return TRUE ;
}


/******************************************************************/
/* Internal function                                              */
/******************************************************************/

BOOL _Scanner_ClamWin_Configure (CLAMWINCONF * pConf) 
{
  TCHAR	szConfFile[MAX_PATH] ;
  TCHAR szLine[1024] ;
  FILE * fp ;
  
  if( ! _Scanner_ClamWin_FindConfFilePath(szConfFile) ) {
    TRACE_ERROR (TEXT("Failed to find ClamWin configuration file\n")) ;
    return FALSE ;
  }

  TRACE_INFO (TEXT("ClamWin configuration file : %s\n"), szConfFile) ;

  fp = _tfopen (szConfFile, TEXT("rt")) ;

  if( ! fp ) {
    TRACE_ERROR (TEXT("Failed to open ClamWin configuration file.\n")) ;
    return FALSE ;
  }

  while( _fgetts(szLine,1024,fp) )
    {
      int i ;
      int iNameFirst, iNameLast ;
      int iValueFirst, iValueLast ;
      LPCTSTR szName, szValue ;

      //TRACE_INFO (TEXT("%s"), szLine) ;

      i = 0 ;

      // skip spaces
      while( _istspace(szLine[i]) ) i++ ;

      // is the line empty ?
      if( ! szLine[i] ) continue ;
      
      // is it a comment ?
      if( szLine[i]==TEXT('#') ) continue ;

      // is it a section ?
      if( szLine[i]==TEXT('[') ) continue ;

      iNameFirst = i ;
      
      // look for '='
      while( szLine[i] && szLine[i]!=TEXT('=') ) i++ ;

      // end of line ?
      if( ! szLine[i] ) continue ;

      iNameLast = i-1 ;

      // remove ending spaces
      while( _istspace(szLine[iNameLast]) ) iNameLast-- ;
      
      // skip spaces
      i++ ;
      while( _istspace(szLine[i]) ) i++ ;
      
      iValueFirst = i ;

      while( szLine[i] && szLine[i]!=TEXT('\n') && szLine[i]!=TEXT('\r') ) i++ ;
      
      iValueLast = i-1 ;

      // remove ending spaces
      while( _istspace(szLine[iValueLast]) ) iValueLast-- ;  

      szName = szLine + iNameFirst ;
      szLine[iNameLast+1] = 0 ;

      szValue = szLine + iValueFirst ;
      szLine[iValueLast+1] = 0 ;

      //TRACE_INFO (TEXT("(%s) = (%s)\n"), szName, szValue) ;

      if( ! _tcscmp(TEXT("clamscan"),szName) )
	{
	  _tcslcpy (pConf->szScanner, szValue, MAX_PATH) ;
	  TRACE_INFO ("Scanner = %s\n", pConf->szScanner) ;
	}
      
      if( ! _tcscmp(TEXT("database"),szName) )
	{
	  _tcslcpy (pConf->szDatabase, szValue, MAX_PATH) ;
	  TRACE_INFO ("Database = %s\n", pConf->szDatabase) ;
	}
    }

  fclose (fp) ;

  return TRUE ;
}

/*
BOOL _Scanner_ClamWin_ScanFile (CLAMWINCONF * pConf, LPCTSTR szFile,
				LPTSTR szOutput, UINT nOutputMax) 
{
  TCHAR szCmdLine[1024] ;
  PROCESS_INFORMATION	pi ;
  DWORD dwExitCode ;
  STARTUPINFO	si = {sizeof(STARTUPINFO)} ;

  wsprintf (szCmdLine, TEXT("\"%s\" -d \"%s\" \"%s\""), 
	    pConf->szScanner, pConf->szDatabase, szFile) ;
  
  TRACE_INFO (TEXT("CmdLine = %s\n"), szCmdLine) ;

  if( ! CreateProcess (NULL, szCmdLine, NULL, NULL, FALSE, 0, 
		       NULL, NULL, &si, &pi) ) {
    TRACE_ERROR (TEXT("Failed to run ClamWin scanner\n")) ;
    return TRUE ;
  }

  WaitForSingleObject (pi.hProcess, 30000) ;

  GetExitCodeProcess (pi.hProcess, &dwExitCode) ;

  TRACE_INFO (TEXT("Scan result = %u (file=%s)\n"), dwExitCode, szFile) ;
  
  CloseHandle (pi.hThread) ;
  CloseHandle (pi.hProcess) ;

  return dwExitCode!=0 ;
}
*/


UINT _Scanner_ClamWin_ScanFile (CLAMWINCONF * pConf, LPCTSTR szFile,
				LPTSTR szOutput, UINT nOutputMax) 
{
  SECURITY_ATTRIBUTES sa = {0};
  STARTUPINFO         si = {0};
  PROCESS_INFORMATION pi = {0};
  HANDLE              hPipeOutputRead  = NULL;
  HANDLE              hPipeOutputWrite = NULL;

  TCHAR		szCmdLine[1024] ;
  DWORD		dwExitCode ;
  DWORD		dwBytesRead ;
  BOOL		bSuccess ;
  UINT		nOutputPos = 0 ;

  wsprintf (szCmdLine, TEXT("\"%s\" -d \"%s\" \"%s\""), 
	    pConf->szScanner, pConf->szDatabase, szFile) ;
  
  TRACE_INFO (TEXT("CmdLine = %s\n"), szCmdLine) ;

  sa.nLength		= sizeof(sa) ;
  sa.bInheritHandle	= TRUE ; 
  sa.lpSecurityDescriptor = NULL ;

  if( ! CreatePipe (&hPipeOutputRead, &hPipeOutputWrite, &sa, 0) )
    TRACE_WARNING (TEXT("CreatePipe failed (error=%d)\n"), GetLastError()) ;
  
  si.cb		= sizeof(si);
  si.dwFlags	= STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
  si.wShowWindow= SW_HIDE;
  si.hStdInput	= GetStdHandle (STD_INPUT_HANDLE) ;
  si.hStdOutput	= hPipeOutputWrite ;
  si.hStdError	= hPipeOutputWrite ;//GetStdHandle (STD_ERROR_HANDLE) ;

  if( ! CreateProcess (NULL, szCmdLine, NULL, NULL, TRUE, 0, 
		       NULL, NULL, &si, &pi) ) {
    TRACE_ERROR (TEXT("Failed to run ClamWin scanner\n")) ;
    CloseHandle (hPipeOutputWrite);
    CloseHandle (hPipeOutputRead);
    return TRUE ;
  }

  CloseHandle (hPipeOutputWrite);

  while( nOutputPos<nOutputMax-1 )
    {
      UINT i ;
      char szBuffer[64] ;

      // try to read pipe
      bSuccess = ReadFile (hPipeOutputRead, 
			   szBuffer, 
			   64,
			   &dwBytesRead, NULL) ;  

      // failed to read ?
      if( !bSuccess || !dwBytesRead ) break ;

      for( i=0 ; i<dwBytesRead ; i++ )
	{
	  switch( szBuffer[i] )
	    {
	    case '\r':
	      break ;
	    case '\n':
	      szOutput[nOutputPos++] = TEXT('\r') ;
	      szOutput[nOutputPos++] = TEXT('\n') ;
	      break ;
	    default:
	      szOutput[nOutputPos++] = szBuffer[i] ;
	    }
	}
      
      //TRACE_INFO (TEXT("%s\n"), szOutput+nOutputPos) ;
    }

  szOutput[nOutputPos] = 0 ;
      
  WaitForSingleObject (pi.hProcess, 30000) ;

  GetExitCodeProcess (pi.hProcess, &dwExitCode) ;

  TRACE_INFO (TEXT("Scan result = %u (file=%s)\n"), dwExitCode, szFile) ;
  
  CloseHandle (hPipeOutputRead);
  CloseHandle (pi.hThread) ;
  CloseHandle (pi.hProcess) ;

  return
    dwExitCode==0 ? SCAN_NO_VIRUS :
    dwExitCode==1 ? SCAN_VIRUS_FOUND :
    SCAN_FAILED ;
}
